/**
 * @author        Nicolas Bottarini <nicolasbottarini@gmail.com>, 404 <http://www.proyecto404.com>
 * @version       $Rev: 3 $
 * @lastrevision  $Date: 2011-09-12 16:45:37 +0000 (Mon, 12 Sep 2011) $	 
 */
package com.proyecto404.core.collections {
	import com.proyecto404.core.BaseObject;
	import com.proyecto404.core.Check;
	import com.proyecto404.core.exception.InvalidOperationException;
	import com.proyecto404.core.exception.NotImplementedException;
	
	public class Set extends BaseObject implements ISet	{
		private var _data:Array;
		private var _currentIndex:int;
		
		public function Set(...rest) {
			var array:Array;
			if (rest.length == 1 && rest[0] is Array) {
				array = rest[0];
			} else {
				array = rest;
			}
			
			_data = new Array();
			_currentIndex = -1;
			
			if (array != null) {
				addFromArray(array);
			}

		}
		
		public function withValue(value:*):ISet {
			add(value);
			return this;
		}
		
		public function withValuesFrom(values:*):ISet {
			if (values is Array) {
				addFromArray(values as Array);
			} else if (values is ICollection) {
				addFromCollection(values as ICollection);
			} else {
				throw new InvalidOperationException("Object argument must be an array or a collection");
			}
			return this;
		}
		
		public function getOne():* {
			Check.require(count() > 0, "The Set is empty");
			
			var value:* = _data[_currentIndex ++];
			if (_currentIndex == _data.length) {
				_currentIndex = 0;
			} 
			return value;
		}
		
		public function union(set:ISet):ISet {
			var newSet:ISet = new Set(set.toArray());
			for(var i:int = 0 ; i < _data.length ; i++) {
				newSet.add(_data[i]);
			}
			return newSet;
		}
		
		public function subtract(set:ISet):ISet {
			var newSet:ISet = new Set(toArray());
			for(var i:int = 0 ; i < _data.length ; i++) {
				newSet.remove(_data[i]);
			}
			return newSet;
		}
		
		public function intersect(set:ISet):ISet {
			var newSet:ISet = new Set();
			for(var i:int = 0 ; i < _data.length ; i++) {
				if (set.contains(_data[i])) {
					newSet.add(_data[i]);
				}
			}
			return newSet;
		}
		
		public function count():int {
			return _data.length;
		}
		
		public function isEmpty():Boolean {
			return count() == 0;
		}
		
		public function contains(value:*):Boolean {
			for(var i:int = 0 ; i < _data.length ; i++) {
				if (areEquals(_data[i], value)) {
					return true;
				}
			}
			return false;
		}
		
		public function clear():void {
			_data = new Array();
			_currentIndex = -1;
		}
		
		public function toList():IList {
			return new List(toArray());
		}
		
		public function toArray():Array {
			return _data;
		}
		
		public function toSet():ISet {
			return this;
		}
		
		public function add(value:*):void {
			if (_currentIndex == -1) {
				_currentIndex = 0;
			}

			if (!contains(value)) {
				_data.push(value);
			}
		}
		
		public function remove(value:*):void {
			var newArray:Array = new Array();
			
			for(var i:int = 0 ; i < _data.length; i++) {
				if (!areEquals(_data[i], value)) {
					newArray.push(_data[i]);
				}
			}
			
			_data = newArray;
			
			if (_currentIndex >= _data.length) {
				_currentIndex = _data.length - 1;
			}
		}
		
		public function removeFromCollection(collection:ICollection):void {
			var it:IIterator = collection.getIterator();
			while (it.hasNext()) {
				remove(it.next());
			}
		}
		
		public function removeFromArray(array:Array):void {
			Check.require(array is Array, "Argument must be an array");
			for(var i:int = 0; i < array.length; i++ ) {
				remove(array[i]);
			}
		}

		public function addFromArray(array:Array):void {
			Check.require(array is Array, "Argument must be an array");
			for(var i:int = 0; i < array.length; i++ ) {
				add(array[i]);
			}
		}
		
		public function addFromCollection(collection:ICollection):void {
			var it:IIterator = collection.getIterator();
			while (it.hasNext()) {
				add(it.next());
			}
		}
		
		public function asReadOnly():ICollection {
			return new ReadOnlySet(this);
		}
		
		public function getIterator():IIterator {
			return new ArrayIterator(toArray());
		}
		
		public function implode(separator:String = ","):String {
			var string:String = "";
			var first:Boolean = true;
			for(var it:IIterator = getIterator() ; it.hasNext() ; ) {
				if (!first) {
					string += separator;
				} else {
					first = false;
				}
				
				string += valueToString(it.next());
			}
			
			return string;
		}
		
		public override function toString():String {
			return "Set{" + implode() + "}";
		}
		
		
		public function collect(criteria:Function):ICollection {
			var newSet:Set = new Set();
			var it:IIterator = getIterator();
			while(it.hasNext()) {
				var item:* = it.next();
				if (criteria.call(this, item)) {
					newSet.add(item);
				}
			}
			return newSet;
		}
		
		public function forEach(callback:Function):void {
			var it:IIterator = getIterator();
			while(it.hasNext()) {
				callback.call(this, it.next());
			}
		}
		
		public function map(convert:Function):ICollection {
			var newSet:Set = new Set();
			var it:IIterator = getIterator();
			while(it.hasNext()) {
				var item:* = it.next();
				newSet.add(convert.call(this, item));
			}
			return newSet;
		}
		
		public function all(criteria:Function):Boolean {
			var it:IIterator = getIterator();
			while(it.hasNext()) {
				var item:* = it.next();
				if (!criteria.call(this, item)) {
					return false;
				}
			}
			return true;
		}
		
		public function any(criteria:Function):Boolean {
			var it:IIterator = getIterator();
			while(it.hasNext()) {
				var item:* = it.next();
				if (criteria.call(this, item)) {
					return true;
				}
			}
			return false;
		}
		
		public override function equals(value:*):Boolean {
			if(!(value is Set)) return false;
			var other:Set = Set(value);
			
			if(count() != other.count()) return false;
			
			var it:IIterator = getIterator();
			while(it.hasNext()) {
				if (!other.contains(it.next())) {
					return false;
				}
			}	
				
			return true;
		}
	}
}